package.path=package.path..";../itemdb/?.lua"
require("xml")
require("lsqlite3")
require("curl")

db = sqlite3.open("armory.db")

local function gettalents(info)
	local char = info:get_first_child("character")
	local name, realm = char:get_attribute("name"), char:get_attribute("realm")

	for data in db:urows("SELECT content FROM cache WHERE url LIKE '%character-talents.xml?r="..curl.escape(realm).."&n="..curl.escape(name).."%'") do
		local talents = xml.parse(data)
		talents = talents:get_first_child("characterInfo")
		talents = talents:get_first_child("talentTab")
		talents = talents:get_first_child("talentTree")
		return talents:get_attribute("value")
	end
end

local function get_linear_regression(data)
	local num, sx, sy = 0, 0, 0
	for x, y in pairs(data) do
		sx = sx + x
		sy = sy + y
		num = num + 1
	end
	if num < 2 then return num end
	local ax, ay = sx / num, sy / num
	local sxx, sxy, syy = 0, 0, 0
	for x, y in pairs(data) do
		local dx, dy = x - ax, y - ay
		sxx = sxx + dx * dx
		sxy = sxy + dx * dy
		syy = syy + dy * dy
	end
	local m = sxy / sxx
	local b = ay - (m * ax)

	return num, m, b
end

local extractors = {
spellcrit = function (char)
	local tab = char:get_first_child("characterTab")
	if not tab then return end
	local stats = tab:get_first_child("baseStats")
	local int = stats:get_first_child("intellect")
	local intellect = tonumber(int:get_attribute("effective"))
	local spellcrit = tonumber(int:get_attribute("critHitPercent"))
	return intellect, spellcrit
end,
crit = function (char)
	local tab = char:get_first_child("characterTab")
	if not tab then return end
	local stats = tab:get_first_child("baseStats")
	local agi = stats:get_first_child("agility")
	local agility = tonumber(agi:get_attribute("effective"))
	local crit = tonumber(agi:get_attribute("critHitPercent"))
	return agility, crit
end,
blockvalue = function (char)
	local tab = char:get_first_child("characterTab")
	if not tab then return end
	local stats = tab:get_first_child("baseStats")
	local str = stats:get_first_child("strength")
	local strength = tonumber(str:get_attribute("effective"))
	local block = tonumber(str:get_attribute("block"))
	return strength, block
end,
dodge = function (info)
	local char = info:get_first_child("character")
	local tab = info:get_first_child("characterTab")
	if not tab then return end
	local stats = tab:get_first_child("baseStats")
	local agi = stats:get_first_child("agility")
	local agility = tonumber(agi:get_attribute("effective"))
	local defenses = tab:get_first_child("defenses")
	local def = defenses:get_first_child("defense")
	local dodge = defenses:get_first_child("dodge")
	local base_dodge = tonumber(dodge:get_attribute("percent"))
	local def_value = tonumber(def:get_attribute("value"))
	local expected_def = 5 * tonumber(char:get_attribute("level"))
	base_dodge = base_dodge - tonumber(dodge:get_attribute("increasePercent"))
	if expected_def > def_value then
		base_dodge = base_dodge + (expected_def - def_value) * 0.04
	else
		base_dodge = base_dodge - tonumber(def:get_attribute("increasePercent"))
	end
	local race = char:get_attribute("race")
	if race == "Night Elf" then
		base_dodge = base_dodge - 1
	end
	local class = char:get_attribute("class")
	if class == "Rogue" then
		local talents = assert(gettalents(info))
		local index = 24 -- lightning reflexes
		local n = tonumber(talents:sub(index, index)) or 0
		base_dodge = base_dodge - n
	elseif class == "Hunter" then
		local talents = assert(gettalents(info))
		local index = 19 -- Catlike reflexes
		local n = tonumber(talents:sub(index, index)) or 0
		base_dodge = base_dodge - n
		local buffs = tab:get_first_child("buffs")
		for buff in buffs:iterate_children("spell") do
			local name = buff:get_attribute("name")
			if name == "Aspect of the Monkey" then
				base_dodge = base_dodge - 8
			end
		end
	elseif class == "Shaman" then
		local talents = assert(gettalents(info))
		local index = 29 -- Anticipation
		local n = tonumber(talents:sub(index, index)) or 0
		base_dodge = base_dodge - n
	elseif class == "Druid" then
		local shapeshifted
		local buffs = tab:get_first_child("buffs")
		for buff in buffs:iterate_children("spell") do
			local name = buff:get_attribute("name")
			if name == "Cat Form" or name == "Dire Bear Form" or name == "Bear Form" then
				shapeshifted = true
				break
			end
		end
		if shapeshifted then
			local talents = assert(gettalents(info))
			local index = 27 -- Feral Swiftness
			local n = tonumber(talents:sub(index, index)) or 0
			base_dodge = base_dodge - n * 2
		end
	end
	return agility, base_dodge
end,
}

local function get_entries(extractor)
	local entries = {}

	for data in db:urows("SELECT content FROM cache WHERE url LIKE '%character-sheet%'") do
		data = xml.parse(data)
		local info = data:get_first_child("characterInfo")
		local char = info and info:get_first_child("character")
		if char then
			local class = char:get_attribute("class")
			local level = tonumber(char:get_attribute("level"))
			local stat, value = extractor(info)
			if stat then
				local list = entries[class]
				if not list then
					list = {}
					entries[class] = list
				end
				local entry = list[level]
				if not entry then
					entry = {}
					list[level] = entry
				end
				if entry[stat] and entry[stat] ~= value and math.abs(value - entry[stat]) > 1 then
					print("MISMATCH", char:get_attribute("name"), class, level, stat, value - entry[stat])
				else
					entry[stat] = value
				end
			end
		end
	end
	return entries
end

local function print_entries_data(entries)
	for class, list in pairs(entries) do
		local slopes = {}
		local sum_error, count_error = 0, 0
		local sum_base = 0
		local count = 0
		for level = 1, 60 do
			local data = list[level]
			if data then
				local num, m, b = get_linear_regression(data)
				if m then
					slopes[level] = 1 / m
					sum_base = sum_base + b
					count = count + 1
				end
			end
		end
		local n60, m60, b60 = get_linear_regression(slopes)
		for level, value in pairs(slopes) do
			sum_error = sum_error + math.abs(value - (m60 * level + b60))
			count_error = count_error + 1
		end

		slopes = { [60] = slopes[60] }
		for level = 61, 70 do
			local data = list[level]
			if data then
				local num, m, b = get_linear_regression(data)
				if m then
					slopes[level] = 1 / m
					sum_base = sum_base + b
					count = count + 1
				end
			end
		end
		local n70, m70, b70 = get_linear_regression(slopes)
		for level, value in pairs(slopes) do
			sum_error = sum_error + math.abs(value - (m70 * level + b70))
			count_error = count_error + 1
		end
		local base = sum_base / count
		local d60 = math.abs((b70 + 60 * m70) - (b60 + 60 * m60))
		local e70 = b70 + 70 * m70
		local expected = (5 - base) * e70
		local error = sum_error / count_error
		print(("%s = { %.2f, %.2f, %.2f, %.2f, %.2f, }, -- %.1f, %.1f, %.1f, %.1f"):format(class, m60, b60, m70, b70, base, error, d60, e70, expected))
	end
end

for name, extractor in pairs(extractors) do
	print("--", name)
	print_entries_data(get_entries(extractor))
end
